use crate::ast::*;
use crate::error::{CompilerError, Result};
use crate::llvm_backend::LLVMBackend;
use inkwell::values::BasicValueEnum;

impl<'ctx> LLVMBackend<'ctx> {


    /// Generate and() method - combines two Options
    /// If first is Some, returns other; otherwise returns None
    pub(super) fn generate_option_and(
        &mut self,
        option_value: BasicValueEnum<'ctx>,
        _option_type: &crate::ast::types::OptionType,
        arguments: &[Expression],
        location: &crate::error::Location,
    ) -> Result<BasicValueEnum<'ctx>> {
        if arguments.len() != 1 {
            return Err(CompilerError::type_error(
                location.line,
                location.column,
                &format!(
                    "Option.and() expects 1 argument (other Option), got {}",
                    arguments.len()
                ),
            ));
        }

        let func = self
            .current_function
            .ok_or_else(|| CompilerError::internal("Cannot create blocks outside function"))?;

        // Generate the other Option value
        let other_option_value = self.generate_expression(&arguments[0])?;

        // Infer the other Option type
        let other_option_type = match self.infer_expression_type(&arguments[0]) {
            Some(Type::Option(opt_type)) => opt_type,
            _ => {
                return Err(CompilerError::internal(
                    "Option.and() expects an Option argument",
                ))
            }
        };

        let tag = self.extract_option_tag(option_value)?;
        let zero = self.context.i32_type().const_int(0, false);
        let is_some =
            self.builder
                .build_int_compare(inkwell::IntPredicate::EQ, tag, zero, "and_check")?;

        // Create blocks: some_block (check other), none_block (return None)
        let some_block = self.context.append_basic_block(func, "and_some");
        let none_block = self.context.append_basic_block(func, "and_none");
        let continue_block = self.context.append_basic_block(func, "and_continue");

        self.builder
            .build_conditional_branch(is_some, some_block, none_block)?;

        // Some block: return other Option
        self.builder.position_at_end(some_block);
        self.builder.build_unconditional_branch(continue_block)?;

        // None block: return None with same type as other Option
        self.builder.position_at_end(none_block);
        let none_result = self.create_option_value(1, None, &other_option_type)?;
        self.builder.build_unconditional_branch(continue_block)?;

        // Continue block: return either other Option or None
        self.builder.position_at_end(continue_block);
        let option_llvm_type = self.get_option_llvm_type(&other_option_type)?;
        let phi = self.builder.build_phi(option_llvm_type, "and_result")?;
        phi.add_incoming(&[
            (&other_option_value, some_block),
            (&none_result, none_block),
        ]);
        Ok(phi.as_basic_value())
    }


    /// Generate or() method - combines two Options
    /// If first is Some, returns first; otherwise returns other
    pub(super) fn generate_option_or(
        &mut self,
        option_value: BasicValueEnum<'ctx>,
        option_type: &crate::ast::types::OptionType,
        arguments: &[Expression],
        location: &crate::error::Location,
    ) -> Result<BasicValueEnum<'ctx>> {
        if arguments.len() != 1 {
            return Err(CompilerError::type_error(
                location.line,
                location.column,
                &format!(
                    "Option.or() expects 1 argument (other Option), got {}",
                    arguments.len()
                ),
            ));
        }

        let func = self
            .current_function
            .ok_or_else(|| CompilerError::internal("Cannot create blocks outside function"))?;

        // Generate the other Option value
        let other_option_value = self.generate_expression(&arguments[0])?;

        // Verify the other Option type (but we don't need to use it)
        let _other_option_type = match self.infer_expression_type(&arguments[0]) {
            Some(Type::Option(_opt_type)) => (),
            _ => {
                return Err(CompilerError::internal(
                    "Option.or() expects an Option argument",
                ))
            }
        };

        let tag = self.extract_option_tag(option_value)?;
        let zero = self.context.i32_type().const_int(0, false);
        let is_some =
            self.builder
                .build_int_compare(inkwell::IntPredicate::EQ, tag, zero, "or_check")?;

        // Create blocks: some_block (return first), none_block (return other)
        let some_block = self.context.append_basic_block(func, "or_some");
        let none_block = self.context.append_basic_block(func, "or_none");
        let continue_block = self.context.append_basic_block(func, "or_continue");

        self.builder
            .build_conditional_branch(is_some, some_block, none_block)?;

        // Some block: return first Option
        self.builder.position_at_end(some_block);
        self.builder.build_unconditional_branch(continue_block)?;

        // None block: return other Option
        self.builder.position_at_end(none_block);
        self.builder.build_unconditional_branch(continue_block)?;

        // Continue block: return either first Option or other Option
        self.builder.position_at_end(continue_block);
        let option_llvm_type = self.get_option_llvm_type(option_type)?;
        let phi = self.builder.build_phi(option_llvm_type, "or_result")?;
        phi.add_incoming(&[
            (&option_value, some_block),
            (&other_option_value, none_block),
        ]);
        Ok(phi.as_basic_value())
    }


    /// Generate xor() method - exclusive or of two Options
    /// Returns Some if exactly one is Some, None otherwise
    pub(super) fn generate_option_xor(
        &mut self,
        option_value: BasicValueEnum<'ctx>,
        option_type: &crate::ast::types::OptionType,
        arguments: &[Expression],
        location: &crate::error::Location,
    ) -> Result<BasicValueEnum<'ctx>> {
        if arguments.len() != 1 {
            return Err(CompilerError::type_error(
                location.line,
                location.column,
                &format!(
                    "Option.xor() expects 1 argument (other Option), got {}",
                    arguments.len()
                ),
            ));
        }

        let func = self
            .current_function
            .ok_or_else(|| CompilerError::internal("Cannot create blocks outside function"))?;

        // Generate the other Option value
        let other_option_value = self.generate_expression(&arguments[0])?;

        // Verify the other Option type (but we don't need to use it)
        let _other_option_type = match self.infer_expression_type(&arguments[0]) {
            Some(Type::Option(_opt_type)) => (),
            _ => {
                return Err(CompilerError::internal(
                    "Option.xor() expects an Option argument",
                ))
            }
        };

        let tag = self.extract_option_tag(option_value)?;
        let other_tag = self.extract_option_tag(other_option_value)?;
        let zero = self.context.i32_type().const_int(0, false);
        let is_some = self.builder.build_int_compare(
            inkwell::IntPredicate::EQ,
            tag,
            zero,
            "xor_first_check",
        )?;
        let other_is_some = self.builder.build_int_compare(
            inkwell::IntPredicate::EQ,
            other_tag,
            zero,
            "xor_other_check",
        )?;

        // Create blocks for all combinations
        let both_some_block = self.context.append_basic_block(func, "xor_both_some");
        let first_some_other_none_block = self
            .context
            .append_basic_block(func, "xor_first_some_other_none");
        let first_none_other_some_block = self
            .context
            .append_basic_block(func, "xor_first_none_other_some");
        let both_none_block = self.context.append_basic_block(func, "xor_both_none");
        let continue_block = self.context.append_basic_block(func, "xor_continue");

        // Check first Option, then check other
        self.builder.build_conditional_branch(
            is_some,
            first_some_other_none_block,
            first_none_other_some_block,
        )?;

        // First is Some: check other
        self.builder.position_at_end(first_some_other_none_block);
        self.builder
            .build_conditional_branch(other_is_some, both_some_block, continue_block)?;

        // First is None: check other
        self.builder.position_at_end(first_none_other_some_block);
        self.builder
            .build_conditional_branch(other_is_some, continue_block, both_none_block)?;

        // Both Some: return None
        self.builder.position_at_end(both_some_block);
        let none_result = self.create_option_value(1, None, option_type)?;
        self.builder.build_unconditional_branch(continue_block)?;

        // Both None: return None
        self.builder.position_at_end(both_none_block);
        let none_result2 = self.create_option_value(1, None, option_type)?;
        self.builder.build_unconditional_branch(continue_block)?;

        // Continue block: return Some (if exactly one is Some) or None
        self.builder.position_at_end(continue_block);
        let option_llvm_type = self.get_option_llvm_type(option_type)?;
        let phi = self.builder.build_phi(option_llvm_type, "xor_result")?;
        phi.add_incoming(&[
            (&option_value, first_some_other_none_block),
            (&other_option_value, first_none_other_some_block),
            (&none_result, both_some_block),
            (&none_result2, both_none_block),
        ]);
        Ok(phi.as_basic_value())
    }

}
